- 받아들일 수 있는 위치기반 인자의 자료형과 개수 지정
- 그러한 특성을 함수마다 다르게 적용
# @statically_typed: 함수나 메서드, 클래스를 유일한 인자로 받기때문에 decorator는 아님.
@statically_typed(str, str, return_type=str) # decorator factory
def make_tagged(text, tag):
return "<{0}{1}</{0}>".format(tag, escape(text))
@statically_typed(str, int, str) # 어떤 반환 타입이든 받아들일 수 있음.
def repeat(what, count, separator):
return ((what + separator) * count)[:-len(separator)]
In [9]:
def statically_typed(*types, return_type=None): # Decorator factory
def decorator(function): # 팩토리가 반환할 decorator 함수
@functools.wraps(function)
def wrapper(*args, **kwargs): # Decorator가 반환할 래퍼 함수
if len(args) > len(types):
raise ValueError("too many arguments")
elif len(args) < len(types):
raise ValueError("too few arguments")
for i, (arg, type_) in enumerate(zip(args, types)):
# 자료값의 Type 비교(지정인자, 지정타입 일치여부)
if not isinstance(arg, type_):
raise ValueError("argument {} must be of type {}"
.format(i, type_.__name__))
result = function(*args, **kwargs)
if (return_type is not None and
not isinstance(result, return_type)): # result의 Type 비교
raise ValueError("return value must be of type {}".format(
return_type.__name__))
return result
return wrapper
return decorator
In [10]:
@statically_typed(str, str, return_type=str)
def make_tagged(text, tag):
return "<{0}{1}</{0}>".format(tag, escape(text))
@statically_typed(str, int, str)
def repeat(what, count, separator):
return ((what + separator) * count)[:-len(separator)]
In [13]:
repeat("ABC", "3", ",")
In [12]:
args = ["ABC", 3, ","]
types = [str, int, str]
for i, (arg, type_) in enumerate(zip(args, types)):
print(i, (arg, type_))
def statically_typed(*types, return_type=None): # 1. statically_typed 호출
def decorator(function): # 3. function 호출(유일한 인자로 decorator에 전달)
@functools.wraps(function)
def wrapper(*args, **kwargs):
return result
return wrapper # 4. 저장된 자료형에 따른(가변적) 새로운 wrapper() 반환/진입
return decorator # 2. decorator 반환/진입(statically_typed 인자 저장, closure)
@application.post("/mailinglists/add") # 최종적으로 mailinglist/add 페이지에 접근
@Web.ensure_logged_in # 페이지 접근 전 로그인 확인
def person_add_submit(username):
name = bottle.request.forms.get("name")
try:
id = Data.MailingLists.add(name)
bottle.redirect("/mailinglists/view")
except Data.Sql.Error as err:
return bottle.mako_template("error", url="/mailinglists/add",
text="Add Mailinglist", message=str(err))
def ensure_logged_in(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
# 사용자 계정명 조회
username = bottle.request.get_cookie(COOKIE, secret=secret(bottle.request))
# 사용자 로그인 상태 확인
if username is not None:
# 키워드 인자에 사용자 계정명 추가
kwargs["username"] = username
# 원본 함수 반환
return function(*args, **kwargs)
bottle.redirect("/login")
return wrapper
In [14]:
class Book:
def __init__(self):
self._price = 1000
def get_price(self):
print("Getter gets...")
return self._price
def set_price(self, value):
if type(value) == int:
print("Setter sets...", value)
self._price = value
else:
raise ValueError("Value must be integer")
book = Book()
print(book.get_price())
book.set_price(100)
print(book.get_price())
book.set_price("100원")
property(fget=None, fset=None, fdel=None, doc=None)
# 비어있는 property 인스턴스 생성
price = property()
# fget 설정
price = price.getter(get_price)
# fset 설정
price = price.setter(set_price)
In [15]:
class Book:
def __init__(self):
self._price = 1000
@property # 멤버변수 접근 시: 클래스.메서드
def price(self):
return self._price
@price.setter # 멤버변수 변경 시: 메서드.setter
def price(self, value):
if type(value) == int:
self._price = value
else:
raise ValueError("Value must be integer")
book = Book()
print(book.price)
book.price = 100
print(book.price)
book.price = "100원"
@ensure("title", is_non_empty_str) # 5. 4까지의 결과값 전달, 최종적으로 4개 속성 추가
@ensure("isbn", is_valid_isbn) # 4. 3까지의 결과값 전달
@ensure("price", is_in_range(1, 10000)) # 3. 2까지의 결과값 전달
@ensure("quantity", is_in_range(0, 1000000)) # 2. Book 클래스 객체가 인자로 전달(quantity 추가)
class Book: # 1. Book 클래스 객체 생성
def __init__(self, title, isbn, price, quantity):
self.title = title
self.isbn = isbn
self.price = price
self.quantity = quantity
@property # 읽기전용 property(setter 없음.)
def value(self):
return self.price * self.quantity
In [15]:
# Title 검증: 제목 공란여부 확인
def is_non_empty_str(name, value):
if not isinstance(value, str):
raise ValueError("{} must be of type str".format(name))
if not bool(value):
raise ValueError("{} may not be empty".format(name))
In [16]:
import numbers
In [17]:
# 값의 제한범위 포함여부 및 입력값 숫자여부(numbers.Number) 확인
def is_in_range(minimum=None, maximum=None):
assert minimum is not None or maximum is not None # 디버그 모드에서만 실행
def is_in_range(name, value):
# 숫자여부 검증
if not isinstance(value, numbers.Number):
raise ValueError("{} must be a number".format(name))
# 최소값 조건 검증
if minimum is not None and value < minimum:
raise ValueError("{} {} is too small".format(name, value))
# 최대값 조건 검증
if maximum is not None and value > maximum:
raise ValueError("{} {} is too big".format(name, value))
return is_in_range
In [18]:
def ensure(name, validate, doc=None): # property 이름, 검증 함수, docstring 받음.
def decorator(Class): # Class를 유일한 인자로 받음.
# self.title의 property값은 self.__title에 저장
privateName = "__" + name # 외부접근이 불가능한 이름 생성
def getter(self): # 저장된 속성값 반환함수 생성
# getattr: 객체, 속성명을 인자로 받아 속성값 반환(없으면 오류발생)
return getattr(self, privateName)
def setter(self, value):
# 전달값 검증
validate(name, value)
# setattr: 객체, 속성명, 값을 받아 객체에 값을 해당 속성으로 설정(없으면 새로생성)
setattr(self, privateName, value)
setattr(Class, name, property(getter, setter, doc=doc))
return Class
return decorator
@do_ensure # 각 Ensure 인스턴스를 같은 이름의 property로 변경
class Book:
title= Ensure(in_non_empty,str) # 각 Ensure는 검증 함수 저장
isbn = Ensure(is_valid_isbn)
price = Ensure(is_in_range(1, 10000))
quantity = Ensure(is_in_range(0, 1000000))
def __init__(self, title, isbn, price, quantity):
self.title = title
self.isbn = isbn
self.price = price
self.quantity = quantity
@property
def value(self):
return self.price * self.quantity
In [19]:
class Ensure:
def __init__(self, validate, doc=None):
self.validate = validate
self.doc = doc
In [20]:
def do_ensure(Class):
def make_property(name, attribute):
privateName = "__" + name
def getter(self):
return getattr(self, privateName)
def setter(self, value):
attribute.validate(name, value)
setattr(self, privateName, value)
return property(getter, setter, doc=attribute.doc)
print(Class.__dict__)
for name, attribute in Class.__dict__.items():
if isinstance(attribute, Ensure):
setattr(Class, name, make_property(name, attribute))
print(Class.__dict__)
return Class
In [29]:
def do_ensure(Class):
print(Class.__dict__)
for name, attribute in Class.__dict__.items():
if isinstance(attribute, Ensure):
privateName = "__" + name
def getter(self):
return getattr(self, privateName)
def setter(self, value):
attribute.validate(name, value)
setattr(self, privateName, value)
setattr(Class, name, property(getter, setter, doc=attribute.doc))
print(Class.__dict__)
return Class
In [21]:
@do_ensure
class Book:
title = Ensure(is_non_empty_str)
price = Ensure(is_in_range(1, 10000))
quantity = Ensure(is_in_range(0, 1000000))
def __init__(self, title, price, quantity):
self.title = title
self.price = price
self.quantity = quantity
@property
def value(self):
return self.price * self.quantity
In [36]:
book = Book("get out of my sight", 1000, 1)
In [24]:
book.__dict__
Out[24]:
In [25]:
book.title = 123
In [ ]:
class Mediated:
def __init__(self):
self.mediator = None
def on_change(self):
if self.mediator is not None:
self.mediator.on_change(self)
In [ ]:
class Button(Mediated):
def __init__(self, text=""):
super().__init__()
self.enabled = True
self.text = text
def click(self):
if self.enabled:
self.on_change()
def __str__(self):
return "Button({!r}) {}".format(self.text,
"enabled" if self.enabled else "disabled")
class Text(Mediated):
def __init__(self, text=""):
super().__init__()
self.__text = text
@property
def text(self):
return self.__text
@text.setter
def text(self, text):
if self.text != text:
self.__text = text
self.on_change()
def __str__(self):
return "Text({!r})".format(self.text)
In [ ]:
def mediated(Class):
setattr(Class, "mediator", None)
def on_change(self):
if self.mediator is not None:
self.mediator.on_change(self)
setattr(Class, "on_change", on_change)
return Class
In [ ]:
@mediated
class Button:
def __init__(self, text=""):
super().__init__()
self.enabled = True
self.text = text
def click(self):
if self.enabled:
self.on_change()
def __str__(self):
return "Button({!r}) {}".format(self.text,
"enabled" if self.enabled else "disabled")
@mediated
class Text:
def __init__(self, text=""):
super().__init__()
self.__text = text
@property
def text(self):
return self.__text
@text.setter
def text(self, text):
if self.text != text:
self.__text = text
self.on_change()
def __str__(self):
return "Text({!r})".format(self.text)